package in.co.codedoc.json;

import java.io.ByteArrayInputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;


public class JSONXMLUtil
{
	public static String Encode(JSONValue value)
	{
		return Encode(value,new StringBuffer()).toString();
	}
	
	public static String Encode(String elementName,JSONValue value)
	{
		return Encode(elementName,value,new StringBuffer()).toString();
	}
	
	public static StringBuffer Encode(JSONValue value,StringBuffer buffer)
	{
		return Encode((String)null,value,buffer);
	}
	
	public static StringBuffer Encode(String elementName,JSONValue value,StringBuffer buffer)
	{
		if(value == null)
		{
			return buffer;
		}
		
		if(value instanceof JSONObjectValue)
		{
			return Encode(elementName != null ? elementName : "object",(JSONObjectValue)value,buffer);
		}
		else if(value instanceof JSONArrayValue)
		{
			return Encode(elementName != null ? elementName : "array",(JSONArrayValue)value,buffer);
		}
		else if(value instanceof JSONStringValue)
		{
			return Encode(elementName != null ? elementName : "atom",(JSONStringValue)value,buffer);
		}
		else
		{
			return Encode(elementName != null ? elementName : "atom",value.Encode(),buffer);
		}
	}

	public static StringBuffer Encode(String elementName,JSONStringValue value,StringBuffer buffer)
	{
		return Encode(elementName, value.GetValue(), buffer);
	}

	public static StringBuffer Encode(String elementName,JSONObjectValue objectValue,StringBuffer buffer)
	{
		OpenTag(elementName, buffer);
		
		objectValue.ForEach
		(
			new JSONObjectValue.ForEach<StringBuffer>()
			{
				public StringBuffer Do(int index,JSONValue key,JSONValue value,StringBuffer buffer)
				{
					String keyElementName 
						= (key instanceof JSONStringValue)
							? ((JSONStringValue)key).GetValue()
							: key.Encode();
					
					return Encode(keyElementName, value, buffer);
				}
			},
			buffer
		);
		
		return CloseTag(elementName, buffer);
	}

	public static StringBuffer Encode(String elementName,JSONArrayValue arrayValue,StringBuffer buffer)
	{
		final String oneElementName 
			= elementName.endsWith("s") 
				? elementName.substring(0, elementName.length()-1)
				: (elementName.equals("array") ? "item" : ("one" + elementName));

		OpenTag(elementName, buffer);
		
		arrayValue.ForEach
		(
			new JSONArrayValue.ForEach<StringBuffer>()
			{
				public StringBuffer Do(int index,JSONValue value,StringBuffer buffer)
				{
					return Encode(oneElementName, value, buffer);
				}
			},
			buffer
		);
		
		return CloseTag(elementName,buffer);
	}

	public static StringBuffer Encode(String elementName,String value,StringBuffer buffer)
	{
		OpenTag(elementName,buffer).append(value);
		
		return CloseTag(elementName,buffer);
	}
	
	private static StringBuffer OpenTag(String elementName,StringBuffer buffer)
	{
		return buffer.append('<').append(elementName).append('>');
	}

	private static StringBuffer CloseTag(String elementName,StringBuffer buffer)
	{
		return buffer.append('<').append('/').append(elementName).append('>');
	}
	
	public static JSONValue Decode(String xml)
	{
		try
		{
			DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
			
			dbf.setValidating(false);
			
			DocumentBuilder db = dbf.newDocumentBuilder();
			
			Document document = db.parse(new ByteArrayInputStream(xml.getBytes()));
			
			return Decode(document.getDocumentElement());
		}
		catch(Throwable th)
		{
			Logger.logger.error("Failed to parse xml:" + th.getMessage());
			
			throw new RuntimeException(th);
		}
	}
	
	private static JSONValue Decode(Element element)
	{
		String containerType = element.getAttribute("containerType");
		
		NodeList childNodes = element.getChildNodes();

		if
		(
			((containerType == null) || (containerType.equals("")))
			&&
			(
				(childNodes.getLength() == 0) 
				||
				((childNodes.getLength() == 1)&&(childNodes.item(0).getNodeType() == Node.TEXT_NODE))
			)
		)
		{
			return new JSONStringValue(element.getTextContent());
		}
		else
		{
			if(containerType == null)
			{
				containerType = "object";
			}
			
			List<Element> childElements = new ArrayList<Element>();
			
			for(int i = 0; i< childNodes.getLength();++i)
			{
				if(childNodes.item(i).getNodeType() == Node.ELEMENT_NODE)
				{
					childElements.add((Element)childNodes.item(i));
				}
			}
			
			Set<String> elementNames = new HashSet<String>();
			
			for(Element childElement:childElements)
			{
				elementNames.add(childElement.getTagName());
			}			
			
			if(containerType.equalsIgnoreCase("array")||(elementNames.size() == 1 && (childElements.size() > 1)))
			{
				//BUG: if there is one child, there is an ambiguity about whether to make this 
				//an array or an object
				JSONArrayValue array = new JSONArrayValue();
				for(Element childElement:childElements)
				{
					array.Add(Decode(childElement));
				}
				
				return array;
			}
			else
			{
				JSONObjectValue object = new JSONObjectValue();
				for(Element childElement:childElements)
				{
					object.SetProperty(childElement.getTagName(),Decode(childElement));
				}
				
				return object;
			}
		}		
	}
}
